package com.sailing.xjpb.elasticsearch;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.ssl.SSLContexts;
import org.elasticsearch.client.RestClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;

import javax.annotation.PostConstruct;
import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.security.KeyStore;
import java.util.Objects;

/**
 * Description: es客户端
 *
 * @author 种传民
 * CreateDate: 2023-03-21
 */
@Slf4j
@PropertySource(value = {"classpath:application-es.properties"})
public class EsClientPool {

    @Value("${es.cluster.username}")
    private String username;
    @Value("${es.cluster.password}")
    private String password;
    @Value("${es.cluster.clusterUrls}")
    private String esServerHosts;
    @Value("${es.certificates.p12.path}")
    private String esCertificatesP12Path;
    @Value("${es.certificates.enable}")
    private boolean esCertificatesEnable;
    @Value("${es.certificates.p12.password}")
    private String esCertificatesP12Password;
    @Value("${es.cluster.connect.maxConnTotal:30}")
    private int maxConnTotal;
    @Value("${es.cluster.connect.maxConnPerRoute:10}")
    private int maxConnPerRoute;

    private ElasticsearchClient elasticsearchClient = null;

    @PostConstruct
    private void init() {
        try {
            HttpHost[] httpHost = this.getHttpHost();
            RestClient restClient = RestClient.builder(httpHost)
                    .setHttpClientConfigCallback(httpAsyncClientBuilder -> {
                        //最大连接数
                        httpAsyncClientBuilder.setMaxConnTotal(maxConnTotal);
                        //单主机并发最大数
                        httpAsyncClientBuilder.setMaxConnPerRoute(maxConnPerRoute);
                        //判断是否验证
                        if (esCertificatesEnable) {
                            //凭证
                            final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
                            credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));
                            //判断是否有验证文件
                            if (StringUtils.isNotBlank(esCertificatesP12Path)) {
                                httpAsyncClientBuilder.setDefaultCredentialsProvider(credentialsProvider)
                                        .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
                                        .setSSLContext(this.getSSLContext());
                            } else {
                                httpAsyncClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
                            }
                        }
                        return httpAsyncClientBuilder;
                    })
                    .build();

            //使用Jackson映射器创建传输层
            ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
            elasticsearchClient = new ElasticsearchClient(transport);
            log.info("初始化elasticsearchClient成功");
        } catch (Exception e) {
            log.error("初始化elasticsearchClient失败！", e);
        }
    }

    /**
     * Description: 获得客户端
     * CreateUser: 种传民
     * CreateDate:
     * <p>
     * EditDescription:
     * EditUser: 种传民
     * EditDate:
     *
     * @return ElasticsearchClient
     */
    public synchronized ElasticsearchClient getClient() {
        if (null == elasticsearchClient) {
            this.init();
        }
        return this.elasticsearchClient;
    }

    /**
     * Description: 获得文件
     * CreateUser: 种传民
     * CreateDate:
     * <p>
     * EditDescription:
     * EditUser: 种传民
     * EditDate:
     *
     * @return InputStream
     */
    private InputStream getFile() {
        File file = new File(esCertificatesP12Path);
        try {
            return new FileInputStream(file);
        } catch (FileNotFoundException e) {
            log.error("获取ES认证文件失败：{}", esCertificatesP12Path, e);
        }
        return null;
    }

    /**
     * Description: 获得host列表
     * CreateUser: 种传民
     * CreateDate:
     * <p>
     * EditDescription:
     * EditUser: 种传民
     * EditDate:
     *
     * @return HttpHost
     */
    private HttpHost[] getHttpHost() {
        //解析HostList配置信息
        String[] split = esServerHosts.split(",");
        //创建HttpHost数组，其中存放es主机和端口的配置信息
        HttpHost[] httpHostArray = new HttpHost[split.length];
        for (int i = 0; i < split.length; i++) {
            String host = split[i];
            httpHostArray[i] = HttpHost.create(host);
            log.info("初始化es地址信息:" + host);
        }
        return httpHostArray;
    }

    /**
     * Description: 获得 SSLContext
     * CreateUser: 种传民
     * CreateDate:
     * <p>
     * EditDescription:
     * EditUser: 种传民
     * EditDate:
     *
     * @return SSLContext
     */
    private SSLContext getSSLContext() {
        try {
            KeyStore truststore = KeyStore.getInstance("PKCS12");
            String keyStorePwd = Objects.toString(esCertificatesP12Password, "");
            truststore.load(this.getFile(), keyStorePwd.toCharArray());
            return SSLContexts.custom().loadTrustMaterial(truststore, new TrustSelfSignedStrategy()).build();
        } catch (Exception e) {
            log.error("初始化SSLContext失败！", e);
        }
        return null;
    }
}
